import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.planargraph.Node;

import java.util.logging.Level;
import java.util.logging.Logger;
import sim.engine.SimState;
import sim.field.geo.GeomVectorField;
import sim.util.Bag;
import sim.util.geo.GeomPlanarGraph;
import sim.util.geo.GeomPlanarGraphEdge;
import sim.util.geo.MasonGeometry;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import sim.io.geo.ShapeFileImporter;

public class EpsteinGeo extends SimState{
	private static final long serialVersionUID = 1L;
	
	public static final int WIDTH = 700; 
    public static final int HEIGHT = 600; 
    
    /** How many agents in the simulation */ 
	public int numPersons1 = 700;
	public int numPersons2 = 0;
	public int numCops = 40;
	
	public double govtLegitimacyMin1 = 0.82;
	public double govtLegitimacyMax1 = 0.82;
	public double govtLegitimacyMin2 = 0.82;
	public double govtLegitimacyMax2 = 0.82;
	public double threshold = 0.1;
	
	public int activeCount = 0;
	public int quietCount = 0;
	public int jailCount = 0;
	
	public int maxJailTerm = 30;
	
	public double personVision = 3.0;
	public double copVision = 3.0;
	public double basemoveRate = 1.0;
	
	public double portrayalSize = 0.05;
	
	
	/** Fields to hold the associated GIS information */ 
    public GeomVectorField roads = new GeomVectorField(WIDTH, HEIGHT);
    public GeomVectorField districts = new GeomVectorField(WIDTH, HEIGHT);
    // where all the agents live
    public GeomVectorField persons = new GeomVectorField(WIDTH, HEIGHT);
    public GeomVectorField cops = new GeomVectorField(WIDTH, HEIGHT);

    // The Importer is responsible for reading in the GIS files.  If you have installed either 
    // GeoTools and/or OGR on your system, you can use those importers.  The ShapeFileImporter does 
    // not rely on external libraries.  
    //OGRImporter importer = new OGRImporter();
    //GeoToolsImporter importer = new GeoToolsImporter();
    ShapeFileImporter importer = new ShapeFileImporter();


    // Stores the walkway network connections.  We represent the walkways as a PlanarGraph, which allows 
    // easy selection of new waypoints for the agents.  
    public GeomPlanarGraph network = new GeomPlanarGraph();
    public GeomVectorField junctions = new GeomVectorField(WIDTH, HEIGHT); // nodes for intersections
    
    //from GridLock.java
    HashMap<Integer, GeomPlanarGraphEdge> idsToEdges = new HashMap<Integer, GeomPlanarGraphEdge>();
    HashMap<GeomPlanarGraphEdge, ArrayList<Person>> edgeTraffic = new HashMap<GeomPlanarGraphEdge, ArrayList<Person>>();
    //ArrayList<person> agentList = new ArrayList<person>();

    public EpsteinGeo(long seed) { super (seed); }
    
    /** adds nodes corresponding to road intersections to GeomVectorField
    *
    * @param nodeIterator Points to first node
    * @param intersections GeomVectorField containing intersection geometry
    *
    * Nodes will belong to a planar graph populated from LineString network.
    */
    private void addIntersectionNodes(@SuppressWarnings("rawtypes") Iterator nodeIterator, GeomVectorField intersections){
    	GeometryFactory fact = new GeometryFactory();
    	Coordinate coord = null;
    	Point point = null;

    	while (nodeIterator.hasNext()){
    		Node node = (Node) nodeIterator.next();
    		coord = node.getCoordinate();
    		point = fact.createPoint(coord);
    		//System.out.println("NodeCoord -- " + coord);
    		//System.out.println("NodePoint -- " + point);

    		junctions.addGeometry(new MasonGeometry(point));
    	}
    }
   
    public void finish(){
    	super.finish();
    }
   
    /** Add agents to the simulation and to the agent GeomVectorField.  Note that each agent does not have
    * any attributes.   */
    void addPeople(){
    	double riskAversion = 0;
    	double perceivedHardship = 0;
    	double govtLegitimacy = 0;
	   
    	for (int i = 0; i < numPersons1; i++){
    		riskAversion = random.nextDouble();
	   	   	perceivedHardship = random.nextDouble();
	   	   	
	   	   	if(govtLegitimacyMin1 == govtLegitimacyMax1){
	   	   		govtLegitimacy = govtLegitimacyMin1;
	   	   	}
	   	   	else{
				boolean generating = true;
				while(generating){
					govtLegitimacy = random.nextDouble();
					if((govtLegitimacy >= govtLegitimacyMin1) && (govtLegitimacy <= govtLegitimacyMax1)){
						generating = false;
					}
				}
	   	   	}
	   	   	Person p = new Person(riskAversion, perceivedHardship, govtLegitimacy);
	   	   	MasonGeometry mg = new MasonGeometry(p.getGeometry(), p); 
	   	   	mg.isMovable = true; 
	   	   	persons.addGeometry(mg);
	   	   	p.start(this);
	   	   	schedule.scheduleRepeating(0, 0, p);
    	}
    	
    	for (int i = 0; i < numPersons2; i++){
    		riskAversion = random.nextDouble();
	   	   	perceivedHardship = random.nextDouble();
	   	   	
	   	   	if(govtLegitimacyMin2 == govtLegitimacyMax2){
	   	   		govtLegitimacy = govtLegitimacyMin2;
	   	   	}
	   	   	else{
				boolean generating = true;
				while(generating){
					govtLegitimacy = random.nextDouble();
					if((govtLegitimacy >= govtLegitimacyMin2) && (govtLegitimacy <= govtLegitimacyMax2)){
						generating = false;
					}
				}
	   	   	}
	   	   	Person p = new Person(riskAversion, perceivedHardship, govtLegitimacy);
	   	   	MasonGeometry mg = new MasonGeometry(p.getGeometry(), p); 
	   	   	mg.isMovable = true; 
	   	   	persons.addGeometry(mg);
	   	   	p.start(this);
	   	   	schedule.scheduleRepeating(0, 0, p);
    	}
    	//System.out.println("MBR People = " + persons.getMBR().getMinX() + "," + persons.getMBR().getMaxX() + " - " + persons.getMBR().getMinY() + "," + persons.getMBR().getMaxY());
    
    	
    }
   
   
    void addCops(){
    	for (int i = 0; i < numCops; i++){
    		Cop c = new Cop();
    		MasonGeometry mgC = new MasonGeometry(c.getGeometry());
    		mgC.isMovable = true;
    		cops.addGeometry(mgC);
    		c.start(this);
    		schedule.scheduleRepeating(0, 0, c);
    	}
    }
   
    public void start(){
    	activeCount = 0;
    	jailCount = 0;
    	quietCount = numPersons1 + numPersons2;
	   
    	super.start();
    	try{
    		//String mainDirectory = "C:/Documents and Settings/jtyates/My Documents/Shapefiles - Iran/DIVA GIS"; 
	   
	   	   	// We want to save the MBR so that we can ensure that all GeomFields
    		// cover identical area.
    	   	System.out.println("reading districts layer");
          
    	   	
    	   	//importer.ingest(mainDirectory + "/IRN_adm2", districts, null);
    	   	importer.ingest("data/iran/IRN_adm2", districts, null);
    		//importer.ingest("data/iraq/IRQ_adm2", districts, null);
         
    	 
    	 	Envelope MBR = districts.getMBR();
           
    	 	System.out.println("reading roads layer");
         
           
    	 	//importer.ingest(mainDirectory + "/IRN_roads", roads, null);
    	 	importer.ingest("data/iran/IRN_roads", roads, null);
    	 	//importer.ingest("data/PhiladelphiaMainRoads", roads, null);
    	 	//importer.ingest("data/HoustonMainRoads_1", roads, null);
    	 	//importer.ingest("data/iraq/IRQ_roads", roads, null);
           
    	 	//Envelope MBR = roads.getMBR();
    	 	MBR.expandToInclude(roads.getMBR());
           
            System.out.println("Done reading data");
           
            // Now synchronize the MBR for all GeomFields to ensure they cover the same area
            districts.setMBR(MBR);
            roads.setMBR(MBR);
           
            //createNetwork(); //from GridLock.java

            network.createFromGeomField(roads);
            addIntersectionNodes(network.nodeIterator(), junctions);
           
            persons.clear(); // clear any existing agents from previous runs
            cops.clear();
            addPeople();
            addCops(); 
           
            JailTermReducer jailTermReducer = new JailTermReducer();
            schedule.scheduleRepeating(0, 1, jailTermReducer);
           
            //System.out.println("MBR PreStart = " + persons.getMBR().getMinX() + "," + persons.getMBR().getMaxX() + " - " + persons.getMBR().getMinY() + "," + persons.getMBR().getMaxY());
            persons.setMBR(MBR);
            //System.out.println("MBR PostStart = " + persons.getMBR().getMinX() + "," + persons.getMBR().getMaxX() + " - " + persons.getMBR().getMinY() + "," + persons.getMBR().getMaxY());
            cops.setMBR(MBR);
           
            //System.out.println("Cop = " + cops.getMBR().getMinX() + "," + cops.getMBR().getMaxX() + "," + cops.getMBR().getMinY() + "," + cops.getMBR().getMaxY());
    	}
    	catch (FileNotFoundException ex){
    		Logger.getLogger(EpsteinGeo.class.getName()).log(Level.SEVERE, null, ex);
    	}
    	
    	
		/*print out averages*/
		double grievanceAvg = 0;
		double perceivedHardshipAvg = 0;
		double riskAversionAvg = 0;
		
		Bag people = persons.getGeometries();
		
		for(Object o : people){
			Person p = (Person)((MasonGeometry) o).userData;
			
			grievanceAvg += p.getGrievance();
			perceivedHardshipAvg += p.getPerceivedHardship();
			riskAversionAvg += p.getRiskAversion();
		}
		grievanceAvg = grievanceAvg/people.size();
		perceivedHardshipAvg = perceivedHardshipAvg/people.size();
		riskAversionAvg = riskAversionAvg/people.size();
		
		System.out.println("\nAverage Grievance:\t\t" + grievanceAvg);
		System.out.println("Average Perceived Hardship:\t" + perceivedHardshipAvg);
		System.out.println("Average Risk Aversion:\t\t" + riskAversionAvg + "\n");
    }
    


	/**
   	private void createNetwork()
   {
	   	System.out.println("creating network...");
	   	network.createFromGeomField(roads);
	   	for (Object o: network.getEdges()){
		   	GeomPlanarGraphEdge e = (GeomPlanarGraphEdge) o;
		   	ArrayList attributes = (ArrayList) e.getLine().getUserData();
		   	for (Object att: attributes)
		   	{
			   	AttributeField a = (AttributeField) att;
			   	if (a.name.equals("ID_ID"))
			   	{
				   	int val = ((Double) a.value).intValue();
				   	idsToEdges.put(val, e);
			   	}
		   	}
		   	e.setData(new ArrayList<person>());
	   	}
	   	addIntersectionNodes(network.nodeIterator(), junctions);
   	}*/
   
    public static void main(String[] args){
    	doLoop(EpsteinGeo.class, args);
    	System.exit(0);
    }
   
    
   	public int getNumPersons1() { return numPersons1; } 
   	public void setNumPersons1(int numPersons1) {this.numPersons1 = numPersons1; }
    public int getNumPersons2() {return numPersons2;}
	public void setNumPersons2(int numPersons2) {this.numPersons2 = numPersons2;}
	public double getGovtLegitimacyMin1() {return govtLegitimacyMin1;}
	public void setGovtLegitimacyMin1(double govtLegitimacyMin1) {this.govtLegitimacyMin1 = govtLegitimacyMin1;}
	public double getGovtLegitimacyMax1() {return govtLegitimacyMax1;}
	public void setGovtLegitimacyMax1(double govtLegitimacyMax1) {this.govtLegitimacyMax1 = govtLegitimacyMax1;}
	public double getGovtLegitimacyMin2() {return govtLegitimacyMin2;}
	public void setGovtLegitimacyMin2(double govtLegitimacyMin2) {this.govtLegitimacyMin2 = govtLegitimacyMin2;}
	public double getGovtLegitimacyMax2() {return govtLegitimacyMax2;}
	public void setGovtLegitimacyMax2(double govtLegitimacyMax2) {this.govtLegitimacyMax2 = govtLegitimacyMax2;}
  	public int getNumCops() {return numCops;}
  	public double getThreshold() {return threshold;}
  	public void setThreshold(double threshold) {this.threshold = threshold;}
  	public int getMaxJailTerm() {return maxJailTerm;}
  	public void setMaxJailTerm(int maxJailTerm) {this.maxJailTerm = maxJailTerm;}
   	public void setNumCops(int n) {numCops = n;}  
	public int getActiveCount() {return activeCount;}
	public void setActiveCount(int activeCount) {this.activeCount = activeCount;}
	public int getQuietCount() {return quietCount;}
	public void setQuietCount(int quietCount) {this.quietCount = quietCount;}
	public int getJailCount() {return jailCount;}
	public void setJailCount(int jailCount) {this.jailCount = jailCount;}
	public double getPersonVision() {return personVision;}
	public void setPersonVision(double personVision) {this.personVision = personVision;}
	public double getCopVision() {return copVision;}
	public void setCopVision(double copVision) {this.copVision = copVision;}
	public double getBasemoveRate() {return basemoveRate;}
	public void setBasemoveRate(double basemoveRate) {this.basemoveRate = basemoveRate;}

	public double getPortrayalSize() {
		return portrayalSize;
	}

	public void setPortrayalSize(double portrayalSize) {
		this.portrayalSize = portrayalSize;
	}
	
    


}